/*
 * Copyright 2010-2024 Eric Kok et al.
 *
 * Transdroid is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Transdroid is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Transdroid.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.transdroid.core.gui.lists;

import android.content.res.Resources;

import org.transdroid.R;
import org.transdroid.daemon.DaemonException;
import org.transdroid.daemon.Torrent;
import org.transdroid.daemon.TorrentStatus;
import org.transdroid.daemon.util.FileSizeConverter;
import org.transdroid.daemon.util.TimespanConverter;

import java.util.Locale;

/**
 * Wrapper around Torrent to provide some addition getters that give translatable or otherwise formatted Strings of
 * torrent statistics.
 *
 * @author Eric Kok
 */
public class LocalTorrent {

    private static final String DECIMAL_FORMATTER = "%.1f";
    private static final String DECIMAL_FORMATTER_2 = "%.2f";
    private final Torrent t;

    private LocalTorrent(Torrent torrent) {
        this.t = torrent;
    }

    /**
     * Creates the LocalTorrent object so that the translatable/formattable version of a Torrent can be used.
     *
     * @param torrent The Torrent object
     * @return The torrent wrapped as LocalTorrent object
     */
    public static LocalTorrent fromTorrent(Torrent torrent) {
        return new LocalTorrent(torrent);
    }

    /**
     * Convert a DaemonException to a translatable human-readable error message
     *
     * @param e The exception that was thrown by the server
     * @return A string resource ID to show to the user
     */
    public static int getResourceForDaemonException(DaemonException e) {
        switch (e.getType()) {
            case MethodUnsupported:
                return R.string.error_unsupported;
            case UnexpectedResponse:
                return R.string.error_jsonresponseerror;
            case ParsingFailed:
                return R.string.error_jsonrequesterror;
            case NotConnected:
                return R.string.error_daemonnotconnected;
            case AuthenticationFailure:
                return R.string.error_401;
            case FileAccessError:
                return R.string.error_torrentfile;
            case MalformedUri:
                return R.string.error_parsinguri;
            case ConnectionError:
            default:
                return R.string.error_httperror;
        }
    }

    /**
     * Builds a string showing the upload/download seed ratio. If not downloading, it will base the ratio on the total
     * size; so if you created the torrent yourself you will have downloaded 0 bytes, but the ratio will pretend you
     * have 100%.
     *
     * @return A nicely formatted string containing the upload/download seed ratio
     */
    public String getRatioString() {
        long baseSize = t.getTotalSize();
        if (t.getStatusCode() == TorrentStatus.Downloading) {
            baseSize = t.getDownloadedEver();
        }
        if (baseSize <= 0) {
            return String.format(Locale.getDefault(), DECIMAL_FORMATTER_2, 0d);
        } else if (t.getRatio() == Double.POSITIVE_INFINITY) {
            return "\u221E";
        } else {
            return String.format(Locale.getDefault(), DECIMAL_FORMATTER_2, t.getRatio());
        }
    }

    /**
     * Returns a formatted string indicating the current progress in terms of transferred bytes
     *
     * @param r                The context resources, to access translations
     * @param withAvailability Whether to show file availability in-line
     * @return A nicely formatted string indicating torrent status and, if applicable, progress in bytes
     */
    public String getProgressSizeText(Resources r, boolean withAvailability) {

        switch (t.getStatusCode()) {
            case Waiting:
            case Error:
                // Not downloading yet
                return r.getString(R.string.status_waitingtodl, FileSizeConverter.getSize(t.getTotalSize()));
            case Checking:
                return r.getString(R.string.status_checking);
            case Downloading:
                // Downloading
                return r.getString(
                        R.string.status_size1,
                        FileSizeConverter.getSize(t.getDownloadedEver()),
                        FileSizeConverter.getSize(t.getTotalSize()),
                        String.format(DECIMAL_FORMATTER, t.getDownloadedPercentage() * 100)
                                + "%"
                                + (!withAvailability ? "" : "/"
                                + String.format(DECIMAL_FORMATTER, t.getAvailability() * 100) + "%"));
            case Seeding:
            case Paused:
            case Queued:
                // Seeding or paused
                return r.getString(R.string.status_size2, FileSizeConverter.getSize(t.getTotalSize()),
                        FileSizeConverter.getSize(t.getUploadedEver()));
            default:
                return "";
        }

    }

    /**
     * Returns a formatted string indicating either the expected time to download (ETA) or, when seeding, the ratio
     *
     * @param r The context resources, to access translations
     * @return A string like '~ 34 seconds', or 'RATIO 8.2' or an empty string
     */
    public String getProgressEtaRatioText(Resources r) {
        switch (t.getStatusCode()) {
            case Downloading:
                // Downloading
                return getRemainingTimeString(r, true, false);
            case Seeding:
            case Paused:
            case Queued:
                // Seeding or paused
                return r.getString(R.string.status_ratio, getRatioString());
            case Waiting:
            case Checking:
            case Error:
            default:
                return "";
        }
    }

    /**
     * Returns a formatted string indicating the torrent status and connected peers
     *
     * @param r The context resources, to access translations
     * @return A string like 'Queued' or, when seeding or leeching, '2 OF 28 PEERS'
     */
    public String getProgressConnectionText(Resources r) {

        switch (t.getStatusCode()) {
            case Waiting:
                return r.getString(R.string.status_waiting);
            case Checking:
                return r.getString(R.string.status_checking);
            case Downloading:
                return r.getString(R.string.status_seeders, t.getSeedersConnected(), t.getSeedersKnown());
            case Seeding:
                return r.getString(R.string.status_leechers, t.getLeechersConnected(), t.getLeechersKnown());
            case Paused:
                return r.getString(R.string.status_paused);
            case Queued:
                return r.getString(R.string.status_stopped);
            case Error:
                return r.getString(R.string.status_error);
            default:
                return r.getString(R.string.status_unknown);
        }

    }

    /**
     * Returns a formatted string indicating current transfer speeds for the torrent
     *
     * @param r The context resources, to access translations
     * @return A string like '↓ 28KB/s ↑ 1.8MB/s', or an empty string when not transferrring
     */
    public String getProgressSpeedText(Resources r) {

        switch (t.getStatusCode()) {
            case Downloading:
                return r.getString(R.string.status_speed_down, FileSizeConverter.getSize(t.getRateDownload()) + "/s") + " "
                        + r.getString(R.string.status_speed_up, FileSizeConverter.getSize(t.getRateUpload()) + "/s");
            case Seeding:
                return r.getString(R.string.status_speed_up, FileSizeConverter.getSize(t.getRateUpload()) + "/s");
            case Waiting:
            case Checking:
            case Paused:
            case Queued:
            default:
                return "";
        }

    }

    public String getProgressStatusEta(Resources r) {
        switch (t.getStatusCode()) {
            case Waiting:
                return r.getString(R.string.status_waiting).toUpperCase(Locale.getDefault());
            case Checking:
                return r.getString(R.string.status_checking).toUpperCase(Locale.getDefault());
            case Error:
                return r.getString(R.string.status_error).toUpperCase(Locale.getDefault());
            case Downloading:
                // Downloading
                return r.getString(R.string.status_downloading).toUpperCase(Locale.getDefault()) + " ("
                        + String.format(DECIMAL_FORMATTER, t.getDownloadedPercentage() * 100) + "%), "
                        + getRemainingTimeString(r, false, true);
            case Seeding:
                return r.getString(R.string.status_seeding).toUpperCase(Locale.getDefault());
            case Paused:
                return r.getString(R.string.status_paused).toUpperCase(Locale.getDefault());
            case Queued:
                return r.getString(R.string.status_queued).toUpperCase(Locale.getDefault());
            default:
                return r.getString(R.string.status_unknown).toUpperCase(Locale.getDefault());
        }
    }

    /**
     * Returns a formatted string indicating the remaining download time
     *
     * @param r      The context resources, to access translations
     * @param inDays Whether to show days or use hours for > 24 hours left instead
     * @return A string like '4d 8h 34m 5s' or '2m 3s'
     */
    public String getRemainingTimeString(Resources r, boolean abbreviate, boolean inDays) {
        if (t.getEta() == -1 || t.getEta() == -2) {
            return r.getString(R.string.status_unknowneta);
        }
        return r.getString(abbreviate ? R.string.status_eta : R.string.status_etalong,
                TimespanConverter.getTime(t.getEta(), inDays));
    }

}
